package stmlib;
import java.util.*;

/**
 *  The standard StmMap allows multiple readers and a single writer.  The ConcurrentStmMap combines N
 *  (default 16) maps into a single dict that allows up to N concurrent writers and more readers.
 *  ConcurrentStmMap delegates concurrency control to the underlying maps.
 */
public class ConcurrentStmMap<K,V> implements Map<K,V> {
    protected final StmMap<K,V>[] maps;
    protected final int N;
    private StmMap<K,V> mapOf(Object key) { return maps[key.hashCode() % N]; }

    public ConcurrentStmMap() { this(16); } //same as ConcurrentHashMap

    public ConcurrentStmMap(final int N) {
        this.N = N; maps = (StmMap<K,V>[])new StmMap[N]; //huh??
        for (int i=0; i < N; i++) maps[i] = new StmMap<K,V>();
    }

    public ConcurrentStmMap(StmMap[] maps) {
        this.N = maps.length;
        this.maps = maps;
    }
    public void clear() { for (int i=0; i<N; i++) maps[i].clear(); }

    public boolean containsKey(Object key) { return mapOf(key).containsKey((K)key); }

    public boolean containsValue(Object value) {
        for (int i=0; i<N; i++) if (maps[i].containsValue((V)value)) return true;
        return false;
    }

    public V get(Object key) { return mapOf(key).get((K)key); }

    public boolean isEmpty() {
        for (int i=0; i<N; i++) if (!maps[i].isEmpty()) return false;
        return true;
    }

    public V put(final K key, V value) { return mapOf(key).put(key, value); }

    public void putAll(Map<? extends K,? extends V> map2) {
        for (Map.Entry<? extends K, ? extends V> entry2 : map2.entrySet()){
            put(entry2.getKey(), entry2.getValue()); //same as dict
        }
    }

    public V remove(final Object key) { return mapOf(key).remove((K)key); }

    public int size() {
        int size = 0;
        for (int i=0; i<N; i++) size += maps[i].size();
        return size;
    }

    public Set<Map.Entry<K,V>> entrySet() { return new EntrySet(); }
    public Set<K> keySet() { return new KeySet(); }
    public Collection<V> values() { return new Values(); }

    class KeySet extends AbstractSet<K> implements Set<K> {
        final ConcurrentStmMap<K,V> parent = ConcurrentStmMap.this;
        public int size() { return parent.size(); }
        public boolean isEmpty() { return parent.isEmpty(); }
        public boolean contains(Object o) { return parent.containsKey((K)o); }
        public boolean remove(Object o) { return (parent.remove((K)o)) != null; }
        public void clear() { parent.clear(); }
        public Iterator<K> iterator () {
            return new Iterator<K>() {
                EntriesIterator source = new EntriesIterator();
                public boolean hasNext() { return source.hasNext(); }
                public K next() { return source.next().getKey(); }
                public void remove() { source.remove(); }
            };
        }
    }

    class EntrySet extends AbstractSet<Map.Entry<K,V>> implements Set<Map.Entry<K,V>> {
        final ConcurrentStmMap<K,V> parent = ConcurrentStmMap.this;
        public int size() { return parent.size(); }
        public boolean isEmpty() { return parent.isEmpty(); }
        public void clear() { parent.clear(); }
        public Iterator<Map.Entry<K,V>> iterator() { return new EntriesIterator(); }
    }

    class Values extends AbstractCollection<V> {
        final ConcurrentStmMap<K,V> parent = ConcurrentStmMap.this;
        public int size() { return parent.size(); }
        public boolean isEmpty() { return parent.isEmpty(); }
        public void clear() { parent.clear(); }
        public Iterator<V> iterator () { 
            return new Iterator<V>() {
                EntriesIterator source = new EntriesIterator();
                public boolean hasNext() { return source.hasNext(); }
                public V next() { return source.next().getValue(); }
                public void remove() { source.remove(); }
            };
        }
    }

    class EntriesIterator implements Iterator<Map.Entry<K,V>> {
        final ConcurrentStmMap<K,V> parent = ConcurrentStmMap.this;
        int curDict = 0;
        Iterator<Map.Entry<K,V>> curIterator = maps[curDict].entrySet().iterator();

        private boolean _hasNext = false;
        public boolean hasNext() {
            if (curIterator.hasNext()) {_hasNext = true; return true;} //sets _hasNext
            else if (curDict+1 >= N) return false;
            else {
                curDict += 1;
                curIterator = maps[curDict].entrySet().iterator();
                return hasNext();
            }
        }

        public Map.Entry<K,V> previouslyReturned;

        public Map.Entry<K,V> next() {
            if (_hasNext || hasNext()) { //check the variable before calling the method.
                previouslyReturned = curIterator.next();
                _hasNext = false; //resets hasNext
                return previouslyReturned;
            } else throw new NoSuchElementException();
        }

        public void remove() {
            //parent.remove(previouslyReturned.getKey());
            curIterator.remove();
        }
    }

    public String toString() {
        StringBuilder s = new StringBuilder();
        for (int i=0; i<N; i++) s.append(maps[i].toString());
        return s.toString();
    }
    public ConcurrentStmMap<K,V> lock(K key) { mapOf(key).lock(); return this; }
    public ConcurrentStmMap<K,V> lock() { for (int i=0;i<N;i++) maps[i].lock(); return this; }

    public static <K,V> ConcurrentStmMap<K,V> makeLRU(int capacity, int nMaps) {
        final StmMap<K,V>[] maps = new StmMap[nMaps];
        final int size = (capacity/nMaps);
        for(int i=0;i<nMaps-1;i++) maps[i] = StmMap.makeLRU(size);
        maps[nMaps - 1] = StmMap.makeLRU(capacity - size *(nMaps - 1)); //so total = totalSize
        return new ConcurrentStmMap<K, V>(maps);
    }
}